home *** CD-ROM | disk | FTP | other *** search
- /**
- * Copyright (c) 2008, Jose Enrique Bolanos, Jorge Villalobos
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * * Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- * * Neither the name of Jose Enrique Bolanos, Jorge Villalobos nor the names
- * of its contributors may be used to endorse or promote products derived
- * from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
- * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- **/
-
- var EXPORTED_SYMBOLS = [];
-
- const Cc = Components.classes;
- const Ci = Components.interfaces;
- const Ce = Components.Exception;
-
- Components.utils.import("resource://firefm/fmCommon.js");
- Components.utils.import("resource://firefm/fmLogin.js");
-
-
- /********************** Start API 2.0 ************************/
-
- // Scrobble client information.
- const SCROBBLE_CLIENT_ID = "ffm";
- const SCROBBLE_CLIENT_VERSION = "1.2";
-
- // The amount of time necessary before a track can be marked to be Scrobbled.
- const SCROBBLE_TIME = 240 * 1000; // 240 seconds.
- // The minimum duration a track should have to be Scrobbled.
- const SCROBBLE_MIN_DURATION = 30 * 1000; // 30 seconds.
-
- // Last.fm URLs.
- const URL_BASE = "http://www.last.fm";
- const URL_SEARCH = URL_BASE + "/music/?q=";
-
- // API URLs.
- const URL_API_20 = "http://ws.audioscrobbler.com/2.0/";
- const URL_SCROBBLE_1_2 = "http://post.audioscrobbler.com:80/";
-
- // Parameters sent to API calls.
-
- const PARAMS_GET_SESSION =
- "?method=auth.getSession&api_key=$(API_KEY)&token=$(TOKEN)&" +
- "api_sig=$(API_SIG)";
- const PARAMS_SIG_GET_SESSION =
- "api_key$(API_KEY)methodauth.getSessiontoken$(TOKEN)";
- const PARAMS_ARTIST_SEARCH =
- "?method=artist.search&artist=$(ARTIST)&api_key=$(API_KEY)&limit=5";
- const PARAMS_TAG_SEARCH =
- "?method=tag.search&api_key=$(API_KEY)&limit=1&tag=$(TAG)";
- const PARAMS_TRACK_LOVE =
- "method=track.love&api_key=$(API_KEY)&api_sig=$(API_SIG)&" +
- "sk=$(SESSION_KEY)&track=$(TRACK)&artist=$(ARTIST)";
- const PARAMS_SIG_TRACK_LOVE =
- "api_key$(API_KEY)artist$(ARTIST)methodtrack.lovesk$(SESSION_KEY)" +
- "track$(TRACK)";
- const PARAMS_TRACK_BAN =
- "method=track.ban&api_key=$(API_KEY)&api_sig=$(API_SIG)&" +
- "sk=$(SESSION_KEY)&track=$(TRACK)&artist=$(ARTIST)";
- const PARAMS_SIG_TRACK_BAN =
- "api_key$(API_KEY)artist$(ARTIST)methodtrack.bansk$(SESSION_KEY)" +
- "track$(TRACK)";
-
- // Radio parameter strings.
- const PARAMS_TUNE_RADIO =
- "method=radio.tune&api_key=$(API_KEY)&api_sig=$(API_SIG)&" +
- "sk=$(SESSION_KEY)&station=$(STATION_URL)";
- const PARAMS_SIG_TUNE_RADIO =
- "api_key$(API_KEY)methodradio.tunesk$(SESSION_KEY)station$(STATION_URL)";
- const PARAMS_GET_PLAYLIST =
- "method=radio.getPlaylist&api_key=$(API_KEY)&api_sig=$(API_SIG)&" +
- "sk=$(SESSION_KEY)&rtp=$(IS_SCROBBLING)";
- const PARAMS_SIG_GET_PLAYLIST =
- "api_key$(API_KEY)methodradio.getPlaylistrtp$(IS_SCROBBLING)" +
- "sk$(SESSION_KEY)";
-
- // Get tags parameter strings
- const PARAMS_ARTIST_GET_TAGS =
- "method=artist.getTags&api_key=$(API_KEY)&api_sig=$(API_SIG)&" +
- "sk=$(SESSION_KEY)&artist=$(ARTIST)";
- const PARAMS_SIG_ARTIST_GET_TAGS =
- "api_key$(API_KEY)artist$(ARTIST)methodartist.getTagssk$(SESSION_KEY)";
- const PARAMS_TRACK_GET_TAGS =
- "method=track.getTags&api_key=$(API_KEY)&api_sig=$(API_SIG)&" +
- "sk=$(SESSION_KEY)&track=$(TRACK)&artist=$(ARTIST)";
- const PARAMS_SIG_TRACK_GET_TAGS =
- "api_key$(API_KEY)artist$(ARTIST)methodtrack.getTagssk$(SESSION_KEY)" +
- "track$(TRACK)";
- const PARAMS_ALBUM_GET_TAGS =
- "method=album.getTags&api_key=$(API_KEY)&api_sig=$(API_SIG)&" +
- "sk=$(SESSION_KEY)&album=$(ALBUM)&artist=$(ARTIST)";
- const PARAMS_SIG_ALBUM_GET_TAGS =
- "album$(ALBUM)api_key$(API_KEY)artist$(ARTIST)methodalbum.getTags" +
- "sk$(SESSION_KEY)";
-
- // Add tags parameter strings
- const PARAMS_ARTIST_ADD_TAGS =
- "method=artist.addTags&api_key=$(API_KEY)&api_sig=$(API_SIG)&" +
- "sk=$(SESSION_KEY)&artist=$(ARTIST)&tags=$(TAGS)";
- const PARAMS_SIG_ARTIST_ADD_TAGS =
- "api_key$(API_KEY)artist$(ARTIST)methodartist.addTagssk$(SESSION_KEY)" +
- "tags$(TAGS)";
- const PARAMS_TRACK_ADD_TAGS =
- "method=track.addTags&api_key=$(API_KEY)&api_sig=$(API_SIG)&" +
- "sk=$(SESSION_KEY)&track=$(TRACK)&artist=$(ARTIST)&tags=$(TAGS)";
- const PARAMS_SIG_TRACK_ADD_TAGS =
- "api_key$(API_KEY)artist$(ARTIST)methodtrack.addTagssk$(SESSION_KEY)" +
- "tags$(TAGS)track$(TRACK)";
- const PARAMS_ALBUM_ADD_TAGS =
- "method=album.addTags&api_key=$(API_KEY)&api_sig=$(API_SIG)&" +
- "sk=$(SESSION_KEY)&album=$(ALBUM)&artist=$(ARTIST)&tags=$(TAGS)";
- const PARAMS_SIG_ALBUM_ADD_TAGS =
- "album$(ALBUM)api_key$(API_KEY)artist$(ARTIST)methodalbum.addTags" +
- "sk$(SESSION_KEY)tags$(TAGS)";
-
- // Remove tags parameter strings
- const PARAMS_ARTIST_REMOVE_TAG =
- "method=artist.removeTag&api_key=$(API_KEY)&api_sig=$(API_SIG)&" +
- "sk=$(SESSION_KEY)&artist=$(ARTIST)&tag=$(TAG)";
- const PARAMS_SIG_ARTIST_REMOVE_TAG =
- "api_key$(API_KEY)artist$(ARTIST)methodartist.removeTagsk$(SESSION_KEY)" +
- "tag$(TAG)";
- const PARAMS_TRACK_REMOVE_TAG =
- "method=track.removeTag&api_key=$(API_KEY)&api_sig=$(API_SIG)&" +
- "sk=$(SESSION_KEY)&track=$(TRACK)&artist=$(ARTIST)&tag=$(TAG)";
- const PARAMS_SIG_TRACK_REMOVE_TAG =
- "api_key$(API_KEY)artist$(ARTIST)methodtrack.removeTagsk$(SESSION_KEY)" +
- "tag$(TAG)track$(TRACK)";
- const PARAMS_ALBUM_REMOVE_TAG =
- "method=album.removeTag&api_key=$(API_KEY)&api_sig=$(API_SIG)&" +
- "sk=$(SESSION_KEY)&album=$(ALBUM)&artist=$(ARTIST)&tag=$(TAG)";
- const PARAMS_SIG_ALBUM_REMOVE_TAG =
- "album$(ALBUM)api_key$(API_KEY)artist$(ARTIST)methodalbum.removeTag" +
- "sk$(SESSION_KEY)tag$(TAG)";
-
- // Scrobble parameter strings.
- const PARAMS_SCROBBLE_HANDSHAKE =
- "?hs=true&p=1.2.1&c=" + encodeURIComponent(SCROBBLE_CLIENT_ID) + "&v=" +
- encodeURIComponent(SCROBBLE_CLIENT_VERSION) + "&u=$(USER)&t=$(TIMESTAMP)" +
- "&a=$(AUTH)&api_key=$(API_KEY)&sk=$(SESSION_KEY)";
- const PARAMS_SCROBBLE_PLAYING =
- "s=$(SCROBBLE_KEY)&a=$(ARTIST)&t=$(TRACK)&b=$(ALBUM)&l=&n=&m=";
- // Parameters for the Scrobble POST.
- const PARAMS_SCROBBLE_SUBMIT =
- [ "s", "a[0]", "t[0]", "b[0]", "o[0]", "m[0]", "n[0]", "l[0]", "i[0]",
- "r[0]" ];
-
- // Sourceforge URL used to submit automated error reports.
- const URL_SOURCEFORGE_SUBMIT =
- "https://sourceforge.net/tracker/?group_id=226773&atid=1120935&" +
- "func=postadd&category_id=100&artifact_group_id=100&summary=$(SUMMARY)&" +
- "details=$(DETAILS)&submit=SUBMIT";
-
-
- /********************** End API 2.0 *************************/
-
- // Regex used to obtained the embedded video inside a last.fm track page
- const REGEX_TRACK_VIDEO = /<div id="?lfmEmbed_(\n|.)+?<\/div>/gi;
-
- // TODO: Is this still needed?
- // Last.fm URLs.
- const URL_EXT_BASE = "http://ext.last.fm";
-
- /**
- * Handles most of the communication with the Last.FM API. See FireFM.Login for
- * other site interactions.
- */
- FireFM.Remote = {
- // Topic notifications sent from this object.
- get TOPIC_TRACK_LOVED() { return "firefm-track-loved"; },
-
- /* Home URL. */
- get URL_HOME() { return URL_BASE; },
- /* Scrobble Help URL. */
- get URL_SCROBBLE_HELP() {
- return "http://firefm.sourceforge.net/help/#scrobbling"; },
- /* Mouse gestures Help URL. */
- get URL_GESTURES_HELP() {
- return "http://firefm.sourceforge.net/help/#gestures"; },
-
- // Tag type constants
- get TAG_TYPE_ARTIST() { return 0; },
- get TAG_TYPE_TRACK() { return 1; },
- get TAG_TYPE_ALBUM() { return 2; },
-
- /* Login Manager service reference. */
- _loginManager : null,
- /* Logger for this object. */
- _logger : null,
- /* Scrobble preference object. */
- _scrobblePref : null,
- /* Indicates if Scrobble is currently active. */
- _scrobbleActive : false,
- /* The Scrobble session ID. */
- _scrobbleSessionId : null,
- /* The URL used to send Now Playing information. */
- _scrobbleURLNowPlaying : null,
- /* The URL used to send Scrobble information. */
- _scrobbleURLSubmit : null,
- /* Stored Last.fm logins. It's a user/hash mapping. */
- _lastFMLogins : null,
- /* Holds the next track to be Scrobbled, if any. */
- _toBeScrobbled : null,
- /* Indicates if the current track has been loved or not. */
- _loved : false,
- /* Indicates if the current track has been banned or not. */
- _banned : false,
- /* Indicates if the current track was skipped or not. */
- _skipped : false,
-
- /**
- * Initializes this object.
- */
- init : function() {
- let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
- let that = this;
-
- this._logger = FireFM.getLogger("FireFM.Remote");
- this._logger.debug("init");
-
- this._loginManager =
- Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
-
- this._scrobblePref =
- FireFM.Application.prefs.get(FireFM.PREF_BRANCH + "scrobble");
-
- // set the current value of the Scrobble preference.
- this._scrobbleActive = (true == this._scrobblePref.value);
- // add preference listener for the Scrobble preference.
- this._scrobblePref.events.addListener("change", this);
-
- FireFM.obsService.addObserver(
- this, FireFM.Player.TOPIC_TRACK_LOADED, false);
- },
-
- /**
- * Performs the auth.getSession call to the API. See:
- * http://www.last.fm/api/show?service=125
- * @param aToken the authentication token used to fetch the session.
- */
- authGetSession : function(aToken) {
- this._logger.debug("authGetSession");
-
- let that = this;
- let paramsSig = PARAMS_SIG_GET_SESSION;
- let params = PARAMS_GET_SESSION;
-
- // set parameters.
- paramsSig = paramsSig.replace(/\$\(API_KEY\)/, FireFM.Secret.API_KEY);
- params =
- params.replace(
- /\$\(API_KEY\)/, encodeURIComponent(FireFM.Secret.API_KEY));
- paramsSig = paramsSig.replace(/\$\(TOKEN\)/, aToken);
- params = params.replace(/\$\(TOKEN\)/, encodeURIComponent(aToken));
- // sign call.
- params =
- params.replace(
- /\$\(API_SIG\)/, FireFM.Secret.generateSignature(paramsSig));
-
- this._sendRequest(
- URL_API_20 + params,
- function(aEvent) { that._authGetSessionLoad(aEvent); },
- function(aEvent) { that._defaultError("authGetSession", aEvent); },
- null, false, null);
- },
-
- /**
- * Load callback handler for the auth.getSession call.
- * @param aEvent the event that triggered this function.
- */
- _authGetSessionLoad : function(aEvent) {
- this._logger.trace("_authGetSessionLoad");
-
- try {
- let doc = aEvent.target.responseXML;
- let keyNode;
-
- if ("ok" == doc.documentElement.getAttribute("status")) {
- keyNode = doc.getElementsByTagName("key")[0];
- FireFM.Login.apiSession = keyNode.textContent;
- // immediately request a Scrobble session.
- this.scrobbleHandshake();
- } else {
- this._logger.error(
- "_authGetSessionLoad. Invalid data received: " +
- aEvent.target.responseText);
- FireFM.obsService.notifyObservers(
- null, FireFM.Login.TOPIC_USER_AUTHENTICATION, null);
- }
- } catch (e) {
- this._logger.error(
- "_authGetSessionLoad. Invalid data received: " +
- aEvent.target.responseText + "\nError:\n" + e);
- FireFM.obsService.notifyObservers(
- null, FireFM.Login.TOPIC_USER_AUTHENTICATION, null);
- }
- },
-
- /**
- * Performs the artist.search call to the API. See:
- * http://www.last.fm/api/show?service=272
- * @param aArtistName the name of the artist to search.
- * @param aCallback the callback function used to return artist information
- * back to the caller. This callback gets an object with {success, result}.
- * 2 results are possible: {true, 'Artist'}, {false, 'URLtoSearchPage'}.
- * Note that the artist in the response can be slightly different from the one
- * given as a parameter. This is because Last.fm corrects common errors such
- * as 'Slipnot' -> 'Slipknot'.
- */
- artistSearch : function(aArtistName, aCallback) {
- this._logger.debug("artistSearch");
-
- if (("string" != typeof(aArtistName)) || (0 == aArtistName.length) ||
- ("function" != typeof(aCallback))) {
- throw "Invalid artist name or callback function.";
- }
-
- let that = this;
- let params = PARAMS_ARTIST_SEARCH;
- let apiKey = encodeURIComponent(FireFM.Secret.API_KEY);
-
- // set parameters.
- params = params.replace(/\$\(API_KEY\)/, apiKey);
- params = params.replace(/\$\(ARTIST\)/, encodeURIComponent(aArtistName));
-
- this._sendRequest(
- URL_API_20 + params,
- function(aEvent) {
- that._artistSearchLoad(aEvent, aArtistName, aCallback); },
- function(aEvent) {
- aCallback(
- { success : false, result : that._getSearchURL(aArtistName) }); },
- null, false, null);
- },
-
- /**
- * Load callback handler for the artist.search call.
- * @param aEvent the event that triggered this function.
- * @param aArtistName the name of the artist to search.
- * @param aCallback the callback function used to return artist information
- * back to the caller.
- */
- _artistSearchLoad : function(aEvent, aArtistName, aCallback) {
- this._logger.trace("_artistSearchLoad");
-
- try {
- let doc = aEvent.target.responseXML;
- let nameNodes = doc.getElementsByTagName("name");
-
- if ((null != nameNodes) && (0 < nameNodes.length)) {
- // default to the first result.
- let artist = nameNodes[0].textContent;
- let name;
-
- // but also look for an exact match.
- for (let i = 0; i < nameNodes.length; i++) {
- name = nameNodes[i].textContent;
-
- if (aArtistName.toLowerCase() == name.toLowerCase()) {
- artist = name;
- break;
- }
- }
-
- aCallback({ success : true, result : artist });
- } else {
- aCallback(
- { success : false, result : this._getSearchURL(aArtistName) });
- }
- } catch (e) {
- this._logger.error(
- "_artistSearchLoad. Invalid data received: " +
- aEvent.target.responseText + "\nError:\n" + e);
- aCallback(
- { success : false, result : this._getSearchURL(aArtistName) });
- }
- },
-
- /**
- * Performs the radio.tune call to the API using the station the user
- * specified.
- * See: http://www.last.fm/api/show?service=160
- */
- tuneRadio : function() {
- this._logger.debug("tuneRadio");
-
- if (null != FireFM.Login.apiSession) {
- let that = this;
- let params = PARAMS_TUNE_RADIO;
- let paramsSig = PARAMS_SIG_TUNE_RADIO;
-
- let apiKey = FireFM.Secret.API_KEY;
- let sessionKey = FireFM.Login.apiSession;
- let stationURL = FireFM.Station.station.getStationURL();
-
- // set parameters to obtain signature
- paramsSig = paramsSig.replace(/\$\(API_KEY\)/, apiKey);
- paramsSig = paramsSig.replace(/\$\(SESSION_KEY\)/, sessionKey);
- paramsSig = paramsSig.replace(/\$\(STATION_URL\)/, stationURL);
-
- // set parameters
- params = params.replace(/\$\(API_KEY\)/, encodeURIComponent(apiKey));
- params =
- params.replace(/\$\(SESSION_KEY\)/,encodeURIComponent(sessionKey));
- params =
- params.replace(/\$\(STATION_URL\)/, encodeURIComponent(stationURL));
- params =
- params.replace(
- /\$\(API_SIG\)/, FireFM.Secret.generateSignature(paramsSig));
-
- this._sendRequest(
- URL_API_20 + "?" + params,
- function(aEvent) { that._tuneRadioLoad(aEvent); },
- function(aEvent) { that._tuneRadioError(aEvent); },
- null, true, this._convertToStream(params));
- } else {
- this._logger.error("tuneRadio. Attempting to play music anonymously.");
- }
- },
-
- /**
- * Load callback handler for the tune radio request.
- * @param aEvent the event that triggered this function.
- */
- _tuneRadioLoad : function(aEvent) {
- this._logger.trace("_tuneRadioLoad");
-
- try {
- let doc = aEvent.target.responseXML;
-
- if ("ok" == doc.documentElement.getAttribute("status")) {
- this._logger.trace("_tuneRadioLoad. Success");
- this.getPlaylist();
- } else {
- this._handleRadioError(doc.getElementsByTagName("error")[0]);
- }
- } catch (e) {
- this._logger.error(
- "_tuneRadioLoad. Invalid data received: " +
- aEvent.target.responseText + "\nError:\n" + e);
- FireFM.obsService.notifyObservers(
- FireFM.Station.station, FireFM.Station.TOPIC_STATION_ERROR,
- FireFM.Station.ERROR_COMMUNICATION_FAILED);
- }
- },
-
- /**
- * Error callback handler for the tune radio request.
- * @param aEvent the event that triggered this function.
- */
- _tuneRadioError : function(aEvent) {
- this._logger.error("_tuneRadioError");
- FireFM.obsService.notifyObservers(
- FireFM.Station.station, FireFM.Station.TOPIC_STATION_ERROR,
- FireFM.Station.ERROR_COMMUNICATION_FAILED);
- this._defaultError("tuneRadio", aEvent);
- },
-
- /**
- * Performs the radio.getPlaylist call to the API.
- * See: http://www.last.fm/api/show?service=256
- */
- getPlaylist : function() {
- this._logger.debug("getPlaylist");
-
- if (null != FireFM.Login.apiSession) {
- let that = this;
- let params = PARAMS_GET_PLAYLIST;
- let paramsSig = PARAMS_SIG_GET_PLAYLIST;
-
- let apiKey = FireFM.Secret.API_KEY;
- let sessionKey = FireFM.Login.apiSession;
- let isScrobbling = (this._scrobbleActive && !FireFM.Private.isPrivate);
-
- // set parameters to obtain signature
- paramsSig = paramsSig.replace(/\$\(API_KEY\)/, apiKey);
- paramsSig = paramsSig.replace(/\$\(SESSION_KEY\)/, sessionKey);
- paramsSig = paramsSig.replace(/\$\(IS_SCROBBLING\)/, isScrobbling);
-
- // set parameters
- params = params.replace(/\$\(API_KEY\)/, encodeURIComponent(apiKey));
- params =
- params.replace(/\$\(SESSION_KEY\)/,encodeURIComponent(sessionKey));
- params =
- params.replace(/\$\(IS_SCROBBLING\)/, encodeURIComponent(isScrobbling));
- params =
- params.replace(
- /\$\(API_SIG\)/, FireFM.Secret.generateSignature(paramsSig));
-
- this._sendRequest(
- URL_API_20 + "?" + params,
- function(aEvent) { that._getPlaylistLoad(aEvent); },
- function(aEvent) { that._getPlaylistError(aEvent); },
- null, true, this._convertToStream(params));
- } else {
- // use the old API if the user is not logged in.
- FireFM.Login.getPlaylist();
- }
- },
-
- /**
- * Load callback handler for the get playlist request.
- * @param aEvent the event that triggered this function.
- */
- _getPlaylistLoad : function(aEvent) {
- this._logger.trace("_getPlaylistLoad");
-
- try {
- let doc = aEvent.target.responseXML;
-
- if ("ok" == doc.documentElement.getAttribute("status")) {
- this._logger.trace("_getPlaylistLoad. Success");
- FireFM.Station.loadPlaylist(doc);
- } else {
- this._handleRadioError(doc.getElementsByTagName("error")[0]);
- }
- } catch (e) {
- this._logger.error(
- "_getPlaylistLoad. Invalid data received: " +
- aEvent.target.responseText + "\nError:\n" + e);
- FireFM.obsService.notifyObservers(
- FireFM.Station.station, FireFM.Station.TOPIC_STATION_ERROR,
- FireFM.Station.ERROR_COMMUNICATION_FAILED);
- }
- },
-
- /**
- * Error callback handler for the get playlist request.
- * @param aEvent the event that triggered this function.
- */
- _getPlaylistError : function(aEvent) {
- this._logger.error("_getPlaylistError");
- FireFM.obsService.notifyObservers(
- FireFM.Station.station, FireFM.Station.TOPIC_STATION_ERROR,
- FireFM.Station.ERROR_COMMUNICATION_FAILED);
- this._defaultError("getPlaylist", aEvent);
- },
-
- /**
- * Handles a radio error response.
- * @param aErrorNode the error node received in the error response.
- */
- _handleRadioError : function(aErrorNode) {
- this._logger.error("_handleRadioError. Error:\n " + aErrorNode.textContent);
-
- let error = FireFM.Station.ERROR_COMMUNICATION_FAILED;
- let errorCode = aErrorNode.getAttribute("code");
-
- switch (errorCode) {
- case "8":
- case "11":
- error = FireFM.Station.ERROR_SERVICE_OFFLINE;
- break;
- case "12":
- error = FireFM.Station.ERROR_NO_SUBSCRIPTION;
- break;
- case "18":
- error = FireFM.Station.ERROR_NO_FREE_PLAYS;
- break;
- }
-
- FireFM.obsService.notifyObservers(
- FireFM.Station.station, FireFM.Station.TOPIC_STATION_ERROR, error);
- },
-
- /**
- * Performs the tag.search call to the API. See:
- * http://www.last.fm/api/show?service=273
- * @param aTagName the name of the tag to search.
- * @param aCallback the callback function used to return tag information back
- * to the caller. This callback gets an object with {success, result}.
- * 2 results are possible: {true, 'tag'}, {false, 'URLtoSearchPage'}.
- * Note that the tag in the response can be slightly different from the one
- * given as a parameter. This is because Last.fm corrects common errors such
- * as 'Slipnot' -> 'Slipknot'.
- */
- tagSearch : function(aTagName, aCallback) {
- this._logger.debug("tagSearch");
-
- if (("string" != typeof(aTagName)) || (0 == aTagName.length) ||
- ("function" != typeof(aCallback))) {
- throw "Invalid tag name or callback function.";
- }
-
- let that = this;
- let params = PARAMS_TAG_SEARCH;
- let apiKey = encodeURIComponent(FireFM.Secret.API_KEY);
-
- // set parameters.
- params = params.replace(/\$\(API_KEY\)/, apiKey);
- params = params.replace(/\$\(TAG\)/, encodeURIComponent(aTagName));
-
- this._sendRequest(
- URL_API_20 + params,
- function(aEvent) { that._tagSearchLoad(aEvent, aTagName, aCallback); },
- function(aEvent) {
- aCallback(
- { success : false, result : that._getSearchURL(aTagName, true) }); },
- null, false, null);
- },
-
- /**
- * Load callback handler for the tag.search call.
- * @param aEvent the event that triggered this function.
- * @param aTagName the name of the tag to search.
- * @param aCallback the callback function used to return tag information back
- * to the caller.
- */
- _tagSearchLoad : function(aEvent, aTagName, aCallback) {
- this._logger.trace("_tagSearchLoad");
-
- try {
- let doc = aEvent.target.responseXML;
- let nameNodes = doc.getElementsByTagName("name");
-
- if ((null != nameNodes) && (0 < nameNodes.length)) {
- let tag = nameNodes[0].textContent;
-
- aCallback({ success : true, result : tag });
- } else {
- aCallback(
- { success : false, result : this._getSearchURL(aTagName, true) });
- }
- } catch (e) {
- this._logger.error(
- "_tagSearchLoad. Invalid data received: " +
- aEvent.target.responseText + "\nError:\n" + e);
- aCallback(
- { success : false, result : this._getSearchURL(aTagName, true) });
- }
- },
-
- /**
- * Generates a search URL for the given string.
- * @param aString the query given by the user.
- * @param aIsTag true if the search URL should be for a tag.
- */
- _getSearchURL : function(aString, aIsTag) {
- this._logger.trace("_getSearchURL");
-
- let url =
- URL_SEARCH + FireFM.encodeFMString(aString) + (aIsTag ? "&m=tags" : "");
-
- return url;
- },
-
- /**
- * Performs the artist.getTags, track.getTags or album.getTags calls to the
- * API, using the given track. The API method called depends on the given tag
- * type. See:
- * http://www.last.fm/api/show?service=318
- * http://www.last.fm/api/show?service=320
- * http://www.last.fm/api/show?service=317
- * @param aTrack The track object to which tags will be added.
- * @param aTagType The type of tag to be added: artist, track or album.
- * @param aCallback The method to be called when the response is received.
- */
- getTags : function(aTrack, aTagType, aCallback) {
- this._logger.debug("getTags");
-
- let that = this;
- let params;
- let paramsSig;
-
- let apiKey = FireFM.Secret.API_KEY;
- let sessionKey = FireFM.Login.apiSession;
-
- switch (aTagType) {
- case this.TAG_TYPE_ARTIST:
- params = PARAMS_ARTIST_GET_TAGS;
- paramsSig = PARAMS_SIG_ARTIST_GET_TAGS;
- break;
- case this.TAG_TYPE_TRACK:
- params = PARAMS_TRACK_GET_TAGS;
- paramsSig = PARAMS_SIG_TRACK_GET_TAGS;
- break;
- case this.TAG_TYPE_ALBUM:
- params = PARAMS_ALBUM_GET_TAGS;
- paramsSig = PARAMS_SIG_ALBUM_GET_TAGS;
- break;
- }
-
- // set parameters to obtain signature
- paramsSig = paramsSig.replace(/\$\(API_KEY\)/, apiKey);
- paramsSig = paramsSig.replace(/\$\(SESSION_KEY\)/, sessionKey);
- paramsSig = paramsSig.replace(/\$\(TRACK\)/, aTrack.title);
- paramsSig = paramsSig.replace(/\$\(ARTIST\)/, aTrack.artist);
- paramsSig = paramsSig.replace(/\$\(ALBUM\)/, aTrack.album);
-
- // set parameters
- params = params.replace(/\$\(API_KEY\)/, encodeURIComponent(apiKey));
- params =
- params.replace(/\$\(SESSION_KEY\)/,encodeURIComponent(sessionKey));
- params = params.replace(/\$\(TRACK\)/, encodeURIComponent(aTrack.title));
- params = params.replace(/\$\(ARTIST\)/, encodeURIComponent(aTrack.artist));
- params = params.replace(/\$\(ALBUM\)/, encodeURIComponent(aTrack.album));
- params =
- params.replace(
- /\$\(API_SIG\)/, FireFM.Secret.generateSignature(paramsSig));
-
- this._sendRequest(
- URL_API_20 + "?" + params,
- function(aEvent) { that._getTagsLoad(aEvent, aCallback); },
- function(aEvent) {
- that._defaultError("getTags", aEvent);
- aCallback([]);
- },
- null, true, this._convertToStream(params));
- },
-
- /**
- * Load callback handler for the getTags request.
- * @param aEvent The event that triggered this function.
- */
- _getTagsLoad : function(aEvent, aCallback) {
- this._logger.trace("_getTagsLoad");
-
- let tags = [];
-
- try {
- let doc = aEvent.target.responseXML;
-
- if ("ok" == doc.documentElement.getAttribute("status")) {
- this._logger.trace("_getTagsLoad. Success");
-
- let tagNodes = doc.getElementsByTagName("tag");
-
- for (let i = 0; i < tagNodes.length; i++) {
- tags.push(tagNodes[i].getElementsByTagName("name")[0].textContent);
- }
-
- } else {
- let error = doc.getElementsByTagName("error")[0].textContent;
- this._logger.error("_getTagsLoad. Failed: " + error);
- }
- } catch (e) {
- this._logger.error(
- "_getTagsLoad. Invalid data received: " +
- aEvent.target.responseText + "\nError:\n" + e);
- }
-
- aCallback(tags);
- },
-
- /**
- * Performs the artist.addTags, track.addTags or album.addTags calls to the
- * API, using the given track. The API method called depends on the given tag
- * type. See:
- * http://www.last.fm/api/show?service=303
- * http://www.last.fm/api/show?service=304
- * http://www.last.fm/api/show?service=302
- * @param aTrack The track object to which tags will be added
- * @param aTagType The type of tag to be added: artist, track or album.
- * @param aTags The array of tags to be added.
- */
- addTags : function(aTrack, aTagType, aTags) {
- this._logger.debug("addTags");
-
- let that = this;
- let params;
- let paramsSig;
-
- let apiKey = FireFM.Secret.API_KEY;
- let sessionKey = FireFM.Login.apiSession;
- let tags = String(aTags);
-
- switch (aTagType) {
- case this.TAG_TYPE_ARTIST:
- params = PARAMS_ARTIST_ADD_TAGS;
- paramsSig = PARAMS_SIG_ARTIST_ADD_TAGS;
- break;
- case this.TAG_TYPE_TRACK:
- params = PARAMS_TRACK_ADD_TAGS;
- paramsSig = PARAMS_SIG_TRACK_ADD_TAGS;
- break;
- case this.TAG_TYPE_ALBUM:
- params = PARAMS_ALBUM_ADD_TAGS;
- paramsSig = PARAMS_SIG_ALBUM_ADD_TAGS;
- break;
- }
-
- // set parameters to obtain signature
- paramsSig = paramsSig.replace(/\$\(API_KEY\)/, apiKey);
- paramsSig = paramsSig.replace(/\$\(SESSION_KEY\)/, sessionKey);
- paramsSig = paramsSig.replace(/\$\(TRACK\)/, aTrack.title);
- paramsSig = paramsSig.replace(/\$\(ARTIST\)/, aTrack.artist);
- paramsSig = paramsSig.replace(/\$\(ALBUM\)/, aTrack.album);
- paramsSig = paramsSig.replace(/\$\(TAGS\)/, tags);
-
- // set parameters
- params = params.replace(/\$\(API_KEY\)/, encodeURIComponent(apiKey));
- params =
- params.replace(/\$\(SESSION_KEY\)/,encodeURIComponent(sessionKey));
- params = params.replace(/\$\(TRACK\)/, encodeURIComponent(aTrack.title));
- params = params.replace(/\$\(ARTIST\)/, encodeURIComponent(aTrack.artist));
- params = params.replace(/\$\(ALBUM\)/, encodeURIComponent(aTrack.album));
- params = params.replace(/\$\(TAGS\)/, encodeURIComponent(tags));
- params =
- params.replace(
- /\$\(API_SIG\)/, FireFM.Secret.generateSignature(paramsSig));
-
- this._sendRequest(
- URL_API_20 + "?" + params,
- function(aEvent) { that._addTagsLoad(aEvent); },
- function(aEvent) { that._defaultError("addTags", aEvent); },
- null, true, this._convertToStream(params));
- },
-
- /**
- * Load callback handler for the addTags request.
- * @param aEvent The event that triggered this function.
- */
- _addTagsLoad : function(aEvent) {
- this._logger.trace("_addTagsLoad");
-
- try {
- let doc = aEvent.target.responseXML;
-
- if ("ok" == doc.documentElement.getAttribute("status")) {
- this._logger.trace("_addTagsLoad. Success");
- } else {
- let error = doc.getElementsByTagName("error")[0].textContent;
- this._logger.error("_addTagsLoad. Failed: " + error);
- }
- } catch (e) {
- this._logger.error(
- "_addTagsLoad. Invalid data received: " +
- aEvent.target.responseText + "\nError:\n" + e);
- }
- },
-
- /**
- * Performs the artist.removeTag, track.removeTag or album.removeTag calls to
- * the API, using the given track. The API method called depends on the given
- * tag type. See:
- * http://www.last.fm/api/show?service=315
- * http://www.last.fm/api/show?service=316
- * http://www.last.fm/api/show?service=314
- * @param aTrack The track object from which the tag will be removed.
- * @param aTagType The type of tag to be removed: artist, track or album.
- * @param aTag The tag to be removed.
- */
- removeTag : function(aTrack, aTagType, aTag) {
- this._logger.debug("removeTag");
-
- let that = this;
- let params;
- let paramsSig;
-
- let apiKey = FireFM.Secret.API_KEY;
- let sessionKey = FireFM.Login.apiSession;
-
- switch (aTagType) {
- case this.TAG_TYPE_ARTIST:
- params = PARAMS_ARTIST_REMOVE_TAG;
- paramsSig = PARAMS_SIG_ARTIST_REMOVE_TAG;
- break;
- case this.TAG_TYPE_TRACK:
- params = PARAMS_TRACK_REMOVE_TAG;
- paramsSig = PARAMS_SIG_TRACK_REMOVE_TAG;
- break;
- case this.TAG_TYPE_ALBUM:
- params = PARAMS_ALBUM_REMOVE_TAG;
- paramsSig = PARAMS_SIG_ALBUM_REMOVE_TAG;
- break;
- }
-
- // set parameters to obtain signature
- paramsSig = paramsSig.replace(/\$\(API_KEY\)/, apiKey);
- paramsSig = paramsSig.replace(/\$\(SESSION_KEY\)/, sessionKey);
- paramsSig = paramsSig.replace(/\$\(TRACK\)/, aTrack.title);
- paramsSig = paramsSig.replace(/\$\(ARTIST\)/, aTrack.artist);
- paramsSig = paramsSig.replace(/\$\(ALBUM\)/, aTrack.album);
- paramsSig = paramsSig.replace(/\$\(TAG\)/, aTag);
-
- // set parameters
- params = params.replace(/\$\(API_KEY\)/, encodeURIComponent(apiKey));
- params =
- params.replace(/\$\(SESSION_KEY\)/,encodeURIComponent(sessionKey));
- params = params.replace(/\$\(TRACK\)/, encodeURIComponent(aTrack.title));
- params = params.replace(/\$\(ARTIST\)/, encodeURIComponent(aTrack.artist));
- params = params.replace(/\$\(ALBUM\)/, encodeURIComponent(aTrack.album));
- params = params.replace(/\$\(TAG\)/, encodeURIComponent(aTag));
- params =
- params.replace(
- /\$\(API_SIG\)/, FireFM.Secret.generateSignature(paramsSig));
-
- this._sendRequest(
- URL_API_20 + "?" + params,
- function(aEvent) { that._removeTagLoad(aEvent); },
- function(aEvent) { that._defaultError("removeTag", aEvent); },
- null, true, this._convertToStream(params));
- },
-
- /**
- * Load callback handler for the removeTag request.
- * @param aEvent The event that triggered this function.
- */
- _removeTagLoad : function(aEvent) {
- this._logger.trace("_removeTagLoad");
-
- try {
- let doc = aEvent.target.responseXML;
-
- if ("ok" == doc.documentElement.getAttribute("status")) {
- this._logger.trace("_removeTagLoad. Success");
- } else {
- let error = doc.getElementsByTagName("error")[0].textContent;
- this._logger.error("_removeTagLoad. Failed: " + error);
- }
- } catch (e) {
- this._logger.error(
- "_removeTagLoad. Invalid data received: " +
- aEvent.target.responseText + "\nError:\n" + e);
- }
- },
-
- /**
- * Performs the track.love call to the API using the track being played.
- * See: http://www.last.fm/api/show?service=260
- */
- loveTrack : function() {
- this._logger.debug("loveTrack");
-
- let that = this;
- let params = PARAMS_TRACK_LOVE;
- let paramsSig = PARAMS_SIG_TRACK_LOVE;
-
- let apiKey = FireFM.Secret.API_KEY;
- let sessionKey = FireFM.Login.apiSession;
- let track = FireFM.Playlist.currentTrack.title;
- let artist = FireFM.Playlist.currentTrack.artist;
-
- // set parameters to obtain signature
- paramsSig = paramsSig.replace(/\$\(API_KEY\)/, apiKey);
- paramsSig = paramsSig.replace(/\$\(SESSION_KEY\)/, sessionKey);
- paramsSig = paramsSig.replace(/\$\(TRACK\)/, track);
- paramsSig = paramsSig.replace(/\$\(ARTIST\)/, artist);
-
- // set parameters
- params = params.replace(/\$\(API_KEY\)/, encodeURIComponent(apiKey));
- params =
- params.replace(/\$\(SESSION_KEY\)/,encodeURIComponent(sessionKey));
- params = params.replace(/\$\(TRACK\)/, encodeURIComponent(track));
- params = params.replace(/\$\(ARTIST\)/, encodeURIComponent(artist));
- params =
- params.replace(
- /\$\(API_SIG\)/, FireFM.Secret.generateSignature(paramsSig));
-
- this._sendRequest(
- URL_API_20 + "?" + params,
- function(aEvent) { that._loveTrackLoad(aEvent); },
- function(aEvent) { that._loveTrackError(aEvent); },
- null, true, this._convertToStream(params));
- },
-
- /**
- * Load callback handler for the love track request.
- * @param aEvent the event that triggered this function.
- */
- _loveTrackLoad : function(aEvent) {
- this._logger.trace("_loveTrackLoad");
-
- try {
- let doc = aEvent.target.responseXML;
-
- if ("ok" == doc.documentElement.getAttribute("status")) {
- this._logger.trace("_loveTrackLoad. Success");
- this._loved = true;
- FireFM.obsService.notifyObservers(null, this.TOPIC_TRACK_LOVED, true);
- } else {
- let error = doc.getElementsByTagName("error")[0].textContent;
- this._logger.error("_loveTrackLoad. Failed: " + error);
- FireFM.obsService.notifyObservers(null, this.TOPIC_TRACK_LOVED, false);
- }
- } catch (e) {
- this._logger.error(
- "_loveTrackLoad. Invalid data received: " +
- aEvent.target.responseText + "\nError:\n" + e);
- FireFM.obsService.notifyObservers(null, this.TOPIC_TRACK_LOVED, false);
- }
- },
-
- /**
- * Error callback handler for the love track request.
- * @param aEvent the event that triggered this function.
- */
- _loveTrackError : function(aEvent) {
- this._logger.error("_loveTrackError");
- FireFM.obsService.notifyObservers(null, this.TOPIC_TRACK_LOVED, false);
- this._defaultError("loveTrack", aEvent);
- },
-
- /**
- * Performs the track.ban call to the API using the track being played.
- * See: http://www.last.fm/api/show?service=260
- */
- banTrack : function() {
- this._logger.debug("banTrack");
-
- let that = this;
- let params = PARAMS_TRACK_BAN;
- let paramsSig = PARAMS_SIG_TRACK_BAN;
-
- let apiKey = FireFM.Secret.API_KEY;
- let sessionKey = FireFM.Login.apiSession;
- let track = FireFM.Playlist.currentTrack.title;
- let artist = FireFM.Playlist.currentTrack.artist;
-
- this._banned = true;
-
- // set parameters to obtain signature
- paramsSig = paramsSig.replace(/\$\(API_KEY\)/, apiKey);
- paramsSig = paramsSig.replace(/\$\(SESSION_KEY\)/, sessionKey);
- paramsSig = paramsSig.replace(/\$\(TRACK\)/, track);
- paramsSig = paramsSig.replace(/\$\(ARTIST\)/, artist);
-
- // set parameters
- params = params.replace(/\$\(API_KEY\)/, encodeURIComponent(apiKey));
- params =
- params.replace(/\$\(SESSION_KEY\)/,encodeURIComponent(sessionKey));
- params = params.replace(/\$\(TRACK\)/, encodeURIComponent(track));
- params = params.replace(/\$\(ARTIST\)/, encodeURIComponent(artist));
- params =
- params.replace(
- /\$\(API_SIG\)/, FireFM.Secret.generateSignature(paramsSig));
-
- this._sendRequest(
- URL_API_20 + "?" + params,
- function(aEvent) { that._banTrackLoad(aEvent); },
- function(aEvent) { that._banTrackError(aEvent); },
- null, true, this._convertToStream(params));
- },
-
- /**
- * Load callback handler for the ban track request.
- * @param aEvent The event that triggered this function.
- */
- _banTrackLoad : function(aEvent) {
- this._logger.trace("_banTrackLoad");
-
- try {
- let doc = aEvent.target.responseXML;
-
- if ("ok" == doc.documentElement.getAttribute("status")) {
- this._logger.trace("_banTrackLoad. Success");
- } else {
- let error = doc.getElementsByTagName("error")[0].textContent;
- this._logger.error("_banTrackLoad. Failed: " + error);
- }
- } catch (e) {
- this._logger.error(
- "_banTrackLoad. Invalid data received: " +
- aEvent.target.responseText + "\nError:\n" + e);
- }
- },
-
- /**
- * Error callback handler for the ban track request.
- * @param aEvent The event that triggered this function.
- */
- _banTrackError : function(aEvent) {
- this._logger.error("_banTrackError");
- this._defaultError("banTrack", aEvent);
- },
-
- /**
- * Sends a handshake request for to begin a Scrobble session.
- * See: http://www.last.fm/api/submissions
- * @param aCallback (optional) function to be called if the handshake is
- * successful.
- */
- scrobbleHandshake : function(aCallback) {
- this._logger.debug("scrobbleHandshake");
-
- let that = this;
- let params = PARAMS_SCROBBLE_HANDSHAKE;
- let timestamp = Math.floor(Date.now() / 1000);
- let auth =
- encodeURIComponent(FireFM.Secret.generateScrobbleAuth(timestamp));
-
- // set parameters.
- params =
- params.replace(/\$\(USER\)/, encodeURIComponent(FireFM.Login.userName));
- params = params.replace(/\$\(TIMESTAMP\)/, timestamp);
- params = params.replace(/\$\(AUTH\)/, auth);
- params =
- params.replace(
- /\$\(API_KEY\)/, encodeURIComponent(FireFM.Secret.API_KEY));
- params =
- params.replace(
- /\$\(SESSION_KEY\)/, encodeURIComponent(FireFM.Login.apiSession));
-
- this._sendRequest(
- URL_SCROBBLE_1_2 + params,
- function(aEvent) { that._scrobbleHandshakeLoad(aEvent, aCallback); },
- function(aEvent) { that._defaultError("scrobbleHandshake", aEvent); },
- null, false, null);
- },
-
- /**
- * Load callback handler for the Scrobble handshake request.
- * @param aEvent the event that triggered this function.
- * @param aCallback (optional) function to be called if the handshake is
- * successful.
- */
- _scrobbleHandshakeLoad : function(aEvent, aCallback) {
- this._logger.trace("_scrobbleHandshakeLoad");
-
- try {
- let data = aEvent.target.responseText;
- let lines = data.split("\n");
-
- // do a little integrity check.
- if ((4 <= lines.length) && ("OK" == lines[0])) {
- this._scrobbleSessionId = lines[1];
- this._scrobbleURLNowPlaying = lines[2].replace(/\:80/, "");
- this._scrobbleURLSubmit = lines[3].replace(/\:80/, "");
- FireFM.obsService.addObserver(
- this, FireFM.Player.TOPIC_PROGRESS_CHANGED, false);
- this._logger.debug("_scrobbleHandshakeLoad. Scrobble data loaded.");
-
- if (aCallback) {
- aCallback();
- }
- } else {
- this._logger.error(
- "_scrobbleHandshakeLoad. Invalid data received: " + data);
- }
- } catch (e) {
- this._logger.error(
- "_scrobbleHandshakeLoad. Invalid data received: " +
- aEvent.target.responseText + "\nError:\n" + e);
- }
- },
-
- /**
- * Sends the 'Now Playing' call to the Scrobble URL, with the information of
- * the given track.
- * See: http://www.last.fm/api/submissions#np
- * @param aTrack the track to send information about.
- */
- _sendNowPlaying : function(aTrack) {
- this._logger.debug("_sendNowPlaying");
-
- if (this._scrobbleActive && (null != this._scrobbleSessionId) &&
- !FireFM.Private.isPrivate) {
- let that = this;
- let params = PARAMS_SCROBBLE_PLAYING;
-
- params = params.replace(/\$\(SCROBBLE_KEY\)/, this._scrobbleSessionId);
- params =
- params.replace(/\$\(ARTIST\)/, encodeURIComponent(aTrack.artist));
- params = params.replace(/\$\(TRACK\)/, encodeURIComponent(aTrack.title));
- params =
- params.replace(/\$\(ALBUM\)/, encodeURIComponent(aTrack.albumTitle));
-
- this._sendRequest(
- this._scrobbleURLNowPlaying,
- function(aEvent) {
- that._logger.debug("_sendNowPlaying: " + aEvent.target.responseText);
- },
- function(aEvent) { that._defaultError("sendNowPlaying", aEvent); },
- null, true, params);
- }
- },
-
- /**
- * Sends the last track to the Scrobble service, if it should.
- * See: http://www.last.fm/api/submissions#subs
- * @param aTrack optional argument that 'forces' a specific track to be
- * Scrobbled, instead of the one in queue.
- */
- scrobbleTrack : function(aTrack) {
- this._logger.debug("scrobbleTrack");
-
- let track = (aTrack ? aTrack : this._toBeScrobbled);
-
- if (this._scrobbleActive && (null != track) && !FireFM.Private.isPrivate) {
- let that = this;
- let rating =
- (this._banned ? "B" : (this._loved ? "L" : (this._skipped ? "S" : "")));
- let postParams =
- [ this._scrobbleSessionId, track.artist, track.title, track.albumTitle,
- ("L" + track.trackAuth), "", "", track.duration, track.startTime,
- rating ];
- let postString = "";
- let inputStream;
-
- // generate the POST string.
- for (let i = 0; i < PARAMS_SCROBBLE_SUBMIT.length; i++) {
- postString +=
- (0 < i ? "&" : "") + encodeURIComponent(PARAMS_SCROBBLE_SUBMIT[i]) +
- "=" + encodeURIComponent(postParams[i]);
- }
-
- inputStream = this._convertToStream(postString);
-
- this._sendRequest(
- this._scrobbleURLSubmit,
- function(aEvent) { that._scrobbleTrackLoad(aEvent, track); },
- function(aEvent) { that._defaultError("scrobbleTrack", aEvent); },
- { "Content-Type" : "application/x-www-form-urlencoded" }, true,
- inputStream);
- }
- },
-
- /**
- * Load callback handler for the Scrobble track request.
- * @param aEvent the event that triggered this function.
- * @param aTrack the scrobbled track. In case of session error, we can
- * Scrobble again.
- */
- _scrobbleTrackLoad : function(aEvent, aTrack) {
- this._logger.trace("_scrobbleTrackLoad");
-
- try {
- let data = aEvent.target.responseText;
-
- if (0 == data.indexOf("OK")) {
- this._logger.trace("_scrobbleTrackLoad. Success");
- } else if (0 == data.indexOf("BADSESSION")) {
- let that = this;
-
- this._logger.warn("_scrobbleTrackLoad. Bad session. Reconnecting.");
-
- try {
- FireFM.obsService.removeObserver(
- this, FireFM.Player.TOPIC_PROGRESS_CHANGED);
- } catch (e) {
- this._logger.warn(
- "_scrobbleTrackLoad. Error removing observer:\n" + e);
- }
-
- // retry the handshake and run the Scrobble call if it works.
- this.scrobbleHandshake(function() { that.scrobbleTrack(aTrack); });
- } else {
- this._logger.error(
- "_scrobbleTrackLoad. Invalid data received: " + data);
- }
- } catch (e) {
- this._logger.error(
- "_scrobbleTrackLoad. Invalid data received: " +
- aEvent.target.responseText + "\nError:\n" + e);
- }
- },
-
- /**
- * Indicates if the track can be Scrobbled or not.
- * See: http://www.last.fm/api/submissions#subs
- * @param aTrack the track to check for Scrobbling.
- * @param aProgress the current progress percentage.
- * @return true if the track can be Scrobbled, false otherwise.
- */
- _canScrobble : function(aTrack, aProgress) {
- // XXX: no logging here for performance reasons.
- let canScrobble =
- ((50 <= parseInt(aProgress, 10)) ||
- (SCROBBLE_TIME <= ((aTrack.duration * aProgress) / 100)));
-
- return canScrobble;
- },
-
- /**
- * Marks the current track as 'skipped'.
- */
- skipTrack : function() {
- this._logger.debug("skipTrack");
- this._skipped = true;
- },
-
- /**
- * Obtains the feed from the specified URL, and notifies the callback handler
- * of the result.
- * @param aURL the URL to fetch the feed from.
- * @param aCallback the callback method for this call. This callback gets the
- * XMLHTTPRequest object, or null in case an error occurs.
- */
- fetchFeed : function(aURL, aCallback) {
- this._logger.debug("fetchFeed");
-
- let that = this;
-
- this._sendRequest(
- aURL, function(aEvent) { aCallback(aEvent.target); },
- function(aEvent) {
- aCallback(null); that._defaultError("fetchFeed", aEvent); },
- null, false);
- },
-
- /**
- * Sends a player load error to our Sourceforge bug tracker.
- * @para aErrorInfo string with the details of the error to be sent.
- */
- sendPlayerLoadError : function(aErrorInfo) {
- this._logger.debug("sendPlayerLoadError");
-
- let timestamp = new Date().getTime();
- let url = URL_SOURCEFORGE_SUBMIT;
- let that = this;
-
- url =
- url.replace(
- /\$\(SUMMARY\)/, encodeURIComponent("Player load error " + timestamp));
- url = url.replace(/\$\(DETAILS\)/, encodeURIComponent(aErrorInfo));
-
- this._sendRequest(
- url, function(aEvent) { that._sendPlayerLoadErrorLoad(aEvent); },
- function(aEvent) { that._defaultError("sendPlayerLoadError", aEvent); },
- { "Content-Type" : "application/x-www-form-urlencoded" }, false, null);
- },
-
- /**
- * Load callback handler for the send player load error request.
- * @param aEvent the event that triggered this function.
- */
- _sendPlayerLoadErrorLoad : function(aEvent) {
- this._logger.error(
- "_sendPlayerLoadErrorLoad. Response: " + aEvent.target.responseText);
- },
-
- /**
- * Opens the track page and looks for the embedded track video.
- * @param aTrack The track object whose video will be obtained.
- * @param aCallback The method to be called if and when a video is found.
- */
- getTrackVideo : function(aTrack, aCallback) {
- this._logger.debug("getTrackVideo");
-
- let that = this;
- this._sendRequest(
- aTrack.trackURL,
- function(aEvent) { that._getTrackVideoLoad(aTrack, aEvent, aCallback); },
- function(aEvent) { that._defaultError("getTrackVideo", aEvent); },
- null, false, null);
- },
-
- /**
- * Handles the response from opening the track page. Parses the response text
- * and looks for an embedded video. If it is found then the callback method
- * is called.
- * @param aTrack The track object whose video is being obtained.
- * @param aEvent The http request event that triggered this method.
- * @param aCallback The method to be called if and when a video is found.
- */
- _getTrackVideoLoad : function(aTrack, aEvent, aCallback) {
- this._logger.trace("_getTrackVideoLoad");
-
- try {
- let videoMatch = REGEX_TRACK_VIDEO.exec(aEvent.target.responseText);
-
- if (videoMatch) {
- aCallback(aTrack, videoMatch[0]);
- }
-
- } catch (e) {
- this._logger.error(
- "_getTrackVideoLoad. Could not parse track page to get video: " +
- aEvent.target.responseText + "\nError:\n" + e);
- }
- },
-
- /**
- * Sends an HTTP request. This is just an utility function to save some code
- * lines.
- * @param aURL the url to send the request to.
- * @param aLoadHandler the load callback handler. Can be null.
- * @param aErrorHandler the error callback handler. Can be null.
- * @param aHeaders object mapping that represents the headers to send. Can be
- * null or empty.
- * @param aIsPOST indicates if the method POST (true) or GET (false).
- * @param aPOSTString the string or stream to send through post (optional).
- */
- _sendRequest : function(
- aURL, aLoadHandler, aErrorHandler, aHeaders, aIsPOST, aPOSTString) {
- this._logger.trace("_sendRequest");
-
- let request =
- Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance();
-
- // add event handlers.
- request.QueryInterface(Ci.nsIDOMEventTarget);
-
- if (null != aLoadHandler) {
- request.addEventListener("load", aLoadHandler, false);
- }
-
- if (null != aErrorHandler) {
- request.addEventListener("error", aErrorHandler, false);
- }
-
- // prepare and send the request.
- request.QueryInterface(Ci.nsIXMLHttpRequest);
- request.open((aIsPOST ? "POST" : "GET"), aURL, true);
-
- if (null != aHeaders) {
- for (let header in aHeaders) {
- request.setRequestHeader(header, aHeaders[header]);
- }
- }
-
- if (aIsPOST) {
- request.send(aPOSTString);
- } else {
- request.send(null);
- }
- },
-
- /**
- * Converts the given string into a UTF-8 string that can be sent through POST
- * as if it were binary. This is required for several Last.fm calls.
- * @param aString the string to convert into a stream.
- * @return nsIInputStream for the given string.
- */
- _convertToStream : function(aString) {
- this._logger.trace("_convertToStream");
-
- let multiStream =
- Cc["@mozilla.org/io/multiplex-input-stream;1"].
- createInstance(Ci.nsIMultiplexInputStream);
- let converter =
- Cc["@mozilla.org/intl/scriptableunicodeconverter"].
- createInstance(Ci.nsIScriptableUnicodeConverter);
- let inputStream;
-
- converter.charset = "UTF-8";
- inputStream = converter.convertToInputStream(aString);
- multiStream.appendStream(inputStream);
-
- return multiStream;
- },
-
- /**
- * Default error callback handler for the asynchronous requests.
- * @param aSource a string that identifies the source of the error.
- * @param aEvent the event that triggered this function.
- */
- _defaultError : function(aSource, aEvent) {
- this._logger.debug("_defaultError");
-
- try {
- this._logger.error(
- "_defaultError. Source: " + aSource + ", status: " +
- aEvent.target.status + ", response: " + aEvent.target.responseText);
- } catch (e) {
- this._logger.error("_defaultError. Error:\n" + e);
- }
- },
-
- /**
- * FUEL event handler. We use it to listen to changes to the Scrobble
- * preference.
- * @param aEvent the event that triggered this function.
- */
- handleEvent : function(aEvent) {
- this._logger.debug("handleEvent");
- this._scrobbleActive = this._scrobblePref.value;
-
- if (this._scrobbleActive && (null != FireFM.Login.userName)) {
- this._sendScrobbleHandshake(false);
- }
- },
-
- /**
- * Observes notifications of cookie and track activity.
- * @param aSubject The object that experienced the change.
- * @param aTopic The topic being observed.
- * @param aData The data related to the change.
- */
- observe : function(aSubject, aTopic, aData) {
- // XXX: there is no logging here for performance purposes.
- switch (aTopic) {
- case FireFM.Player.TOPIC_TRACK_LOADED:
- this.scrobbleTrack();
- this._toBeScrobbled = null;
- this._loved = false;
- this._banned = false;
- this._skipped = false;
- this._sendNowPlaying(aSubject.wrappedJSObject);
- break;
- case FireFM.Player.TOPIC_PROGRESS_CHANGED:
- let currentTrack = FireFM.Playlist.currentTrack;
-
- // check that Scrobbling is active, the track has not already been
- // marked to be Scrobbled, that the duration of the track is at least
- // 30 seconds, and that the track can be Scrobbled depending on its
- // progress.
- if (this._scrobbleActive && (null != this._scrobbleSessionId) &&
- (null == this._toBeScrobbled) &&
- (SCROBBLE_MIN_DURATION <= currentTrack.duration) &&
- this._canScrobble(currentTrack, aData)) {
- this._toBeScrobbled = currentTrack;
- this._logger.debug("observe. This track will be scrobbled.");
- }
-
- break;
- }
- }
- };
-
- /**
- * FireFM.Remote constructor.
- */
- (function() {
- this.init();
- }).apply(FireFM.Remote);
-